﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace RuntimeHelloWorlds
{
	/// <summary>
	/// This is very similar to the ReflectionEmitHelloWorld but rather than
	/// directly writing the IL, you create an expression that comprises the
	/// method body and writes itself into the method.
	/// 
	/// Since Expressions only compile to MethodBuilders defined as static 
	/// this is of limited use, esp. given all the other method there are 
	/// to do runtime code gen.
	/// </summary>
	public class ExpressionReflectionEmitHelloWorld
	{
		public void SayHelloWorld()
		{
			try
			{
				AssemblyName assemblyName = new AssemblyName("ExpressionReflectionEmitHelloWorld");
				AssemblyBuilder assemblyBuilder
					= AppDomain.CurrentDomain
						.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave);

				ModuleBuilder moduleBuilder
					= assemblyBuilder.DefineDynamicModule("ExpressionReflectionEmitHelloWorld");

				TypeBuilder typeBuilder
					= moduleBuilder
						.DefineType("ExpressionReflectionEmitHelloWorld", TypeAttributes.Class, typeof(object));

				ConstructorBuilder constructorBuilder = typeBuilder.DefineDefaultConstructor(MethodAttributes.Public);

				// Important difference!: The method you create must be static, because an Expression 
				// won't compile itself to an instance method
				MethodBuilder methodBuilder
					= typeBuilder.DefineMethod("HelloWorld", MethodAttributes.Public | MethodAttributes.Static);

				MethodInfo methodInfo
					= typeof(Console)
						.GetMethod("WriteLine", new[] { typeof(string) });

				// The Expression stuff starts here:
				ConstantExpression helloWorldText = Expression.Constant("Hello, World! (Expression + Reflection Emit)");
				MethodCallExpression consoleWriteline = Expression.Call(methodInfo, helloWorldText);
				Expression<Action> lambda = Expression.Lambda<Action>(consoleWriteline);
				lambda.CompileToMethod(methodBuilder);

				assemblyBuilder.Save("ExpressionReflectionEmitHelloWorld.dll");
				typeBuilder.CreateType();

				Type type = assemblyBuilder.GetTypes().First();
				MethodInfo helloWorldMethod = type.GetMethods().Where(m => m.Name.Equals("HelloWorld")).First();

				helloWorldMethod.Invoke(null, null);
			}
			catch (Exception ex)
			{

			}
		}
	}
}
